// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder.h>
#include <drm/drm_mipi_dsi.h>

#include "../lombo/lombo_drv.h"

#define T1_WIDTH	(720)
#define T1_HEIGHT	(480)
#define T1_VIC		(2)
#define T1_HBP		(60)
#define T1_HFP		(16)
#define T1_HSW		(62)
#define T1_HACT		(T1_WIDTH)
#define T1_VBP_F1	(30)
#define T1_VFP_F1	(9)
#define T1_VSW_F1	(6)
#define T1_VACT_F1	(T1_HEIGHT)
#define T1_VT_F1	(T1_VBP_F1 + T1_VFP_F1 + T1_VSW_F1 + T1_VACT_F1)
#define T1_VBP_F2	(0)
#define T1_VFP_F2	(0)
#define T1_VSW_F2	(0)
#define T1_VACT_F2	(0)
#define T1_VT_F2	(T1_VBP_F2 + T1_VFP_F2 + T1_VSW_F2 + T1_VACT_F2)
#define T1_DCLK		(27000000)

#define T2_WIDTH	(1280)
#define T2_HEIGHT	(720)
#define T2_VIC		(19)
#define T2_HBP		(220)
#define T2_HFP		(440)
#define T2_HSW		(40)
#define T2_HACT		(T2_WIDTH)
#define T2_VBP_F1	(20)
#define T2_VFP_F1	(5)
#define T2_VSW_F1	(5)
#define T2_VACT_F1	(T2_HEIGHT)
#define T2_VT_F1	(T2_VBP_F1 + T2_VFP_F1 + T2_VSW_F1 + T2_VACT_F1)
#define T2_VBP_F2	(0)
#define T2_VFP_F2	(0)
#define T2_VSW_F2	(0)
#define T2_VACT_F2	(0)
#define T2_VT_F2	(T2_VBP_F2 + T2_VFP_F2 + T2_VSW_F2 + T2_VACT_F2)
#define T2_DCLK		(74250000)

#define T3_WIDTH	(1920)
#define T3_HEIGHT	(1080)
#define T3_VIC		(16)
#define T3_HBP		(148)
#define T3_HFP		(88)
#define T3_HSW		(44)
#define T3_HACT		(T3_WIDTH)
#define T3_VBP_F1	(36)
#define T3_VFP_F1	(4)
#define T3_VSW_F1	(5)
#define T3_VACT_F1	(T3_HEIGHT)
#define T3_VT_F1	(T3_VBP_F1 + T3_VFP_F1 + T3_VSW_F1 + T3_VACT_F1)
#define T3_VBP_F2	(0)
#define T3_VFP_F2	(0)
#define T3_VSW_F2	(0)
#define T3_VACT_F2	(0)
#define T3_VT_F2	(T3_VBP_F2 + T3_VFP_F2 + T3_VSW_F2 + T3_VACT_F2)
#define T3_DCLK		(148500000)

#define MODES_NUM	(3)
#define DCS_MDELAY_FLAG (0)
#define LANE_NUM	(4)

struct bridge_lt8912b {
	struct drm_connector connector;
	struct drm_bridge bridge;
	struct device *dev;
	struct drm_device *drm;
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	u32 modes_idx;
	int reset_gpio;
	int power_gpio;
	int sel_gpio;
};

static struct i2c_client *bridge_i2c_client[3];

static u32 lt8912b_init_common_code[] = {
	/* DigitalClockEn(); */
	/* define _HDMI_Output_ */
	3, 0x90, 0x08, 0xff,	/* Register address: 0x08; Value: 0xff */
	3, 0x90, 0x09, 0x81,
	3, 0x90, 0x0a, 0xff,
	3, 0x90, 0x0b, 0x64,
	3, 0x90, 0x0c, 0xff,
	3, 0x90, 0x44, 0x31,	/* Close LVDS output */
	3, 0x90, 0x51, 0x1f,
	/* TxAnalog(); */
	3, 0x90, 0x31, 0xa1,
	3, 0x90, 0x32, 0xbf,
	/* 0x03 Open HDMI Tx, 0x00 Close HDMI Tx */
	3, 0x90, 0x33, 0x17,
	3, 0x90, 0x37, 0x00,
	3, 0x90, 0x38, 0x22,
	3, 0x90, 0x60, 0x82,
	3, 0x90, 0x3a, 0x00,
	/* CbusAnalog(); */
	3, 0x90, 0x39, 0x45,
	3, 0x90, 0x3b, 0x00,
	/* HDMIPllAnalog(); */
	3, 0x90, 0x44, 0x31,
	3, 0x90, 0x55, 0x44,
	3, 0x90, 0x57, 0x01,
	3, 0x90, 0x5a, 0x02,
	/* MipiBasicSet(); */
	3, 0x92, 0x10, 0x01,	/* 0x05 */
	3, 0x92, 0x11, 0x08,	/* 0x12 */
	3, 0x92, 0x12, 0x04,
	/* 00 4 lane , 01 lane , 02 2 lane , 03 3 lane */
	3, 0x92, 0x13, (LANE_NUM % 0x04),
	3, 0x92, 0x14, 0x00,
	3, 0x92, 0x15, 0x00,
	3, 0x92, 0x1a, 0x03,
	3, 0x92, 0x1b, 0x03,
	3, 0x92, 0x1e, 0x67,	/* hs/vs/de pol hdmi sel pll sel  */
	3, 0x92, 0x2f, 0x0c,	/* fifo_buff_length 12  */
	/* DDSConfig(); */
	3, 0x92, 0x4e, 0x52,
	3, 0x92, 0x4f, 0xde,
	3, 0x92, 0x50, 0xc0,
	3, 0x92, 0x51, 0x80,
	3, 0x92, 0x51, 0x00,
	3, 0x92, 0x1f, 0x5e,
	3, 0x92, 0x20, 0x01,
	3, 0x92, 0x21, 0x2c,
	3, 0x92, 0x22, 0x01,
	3, 0x92, 0x23, 0xfa,
	3, 0x92, 0x24, 0x00,
	3, 0x92, 0x25, 0xc8,
	3, 0x92, 0x26, 0x00,
	3, 0x92, 0x27, 0x5e,
	3, 0x92, 0x28, 0x01,
	3, 0x92, 0x29, 0x2c,
	3, 0x92, 0x2a, 0x01,
	3, 0x92, 0x2b, 0xfa,
	3, 0x92, 0x2c, 0x00,
	3, 0x92, 0x2d, 0xc8,
	3, 0x92, 0x2e, 0x00,
	3, 0x90, 0x03, 0x7f,
	DCS_MDELAY_FLAG, 100,
	3, 0x90, 0x03, 0xff,
	3, 0x92, 0x42, 0x64,
	3, 0x92, 0x43, 0x00,
	3, 0x92, 0x44, 0x04,
	3, 0x92, 0x45, 0x00,
	3, 0x92, 0x46, 0x59,
	3, 0x92, 0x47, 0x00,
	3, 0x92, 0x48, 0xf2,
	3, 0x92, 0x49, 0x06,
	3, 0x92, 0x4a, 0x00,
	3, 0x92, 0x4b, 0x72,
	3, 0x92, 0x4c, 0x45,
	3, 0x92, 0x4d, 0x00,
	3, 0x92, 0x52, 0x08,
	3, 0x92, 0x53, 0x00,
	3, 0x92, 0x54, 0xb2,
	3, 0x92, 0x55, 0x00,
	3, 0x92, 0x56, 0xe4,
	3, 0x92, 0x57, 0x0d,
	3, 0x92, 0x58, 0x00,
	3, 0x92, 0x59, 0xe4,
	3, 0x92, 0x5a, 0x8a,
	3, 0x92, 0x5b, 0x00,
	3, 0x92, 0x5c, 0x34,
	3, 0x92, 0x1e, 0x4f,
	3, 0x92, 0x51, 0x00,
	/* AudioIIsEn();  IIS Input */
	3, 0x90, 0xB2, 0x01,
	3, 0x94, 0x06, 0x08,
	3, 0x94, 0x07, 0xF0,
	3, 0x94, 0x34, 0xE2,
	3, 0x94, 0x3c, 0x41,
	/* Packet config() */
	3, 0x94, 0x3e, 0x0A,
	/* MIPIRxLogicRes(); */
	3, 0x90, 0x03, 0x7f,
	DCS_MDELAY_FLAG, 100,
	3, 0x90, 0x03, 0xff,
	/* MIPIRxLogicRes(); */
	3, 0x92, 0x51, 0x80,
	DCS_MDELAY_FLAG, 100,
	3, 0x92, 0x51, 0x00,

	3, 0x94, 0x44, 0x10,	/* avi packet output RGB0x10 */
	3, 0x94, 0x45, 0x2A,	/*0x19:4:3 ; 0x2A : 16:9 */
};

static u32 lt8912b_init_code[MODES_NUM][72] = {
	{
		3, 0x92, 0x18, (u8)(T1_HSW % 256),	/* hsync  */
		3, 0x92, 0x19, (u8)(T1_VSW_F1 % 256),	/* vsync  */
		3, 0x92, 0x1c, (u8)(T1_HACT % 256),	/* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T1_HACT / 256),	/* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T1_HBP + T1_HFP + T1_HSW + T1_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T1_HBP + T1_HFP + T1_HSW + T1_HACT) / 256),
		3, 0x92, 0x36, (u8)((T1_VT_F1) % 256),	/* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T1_VT_F1) / 256),	/* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T1_VBP_F1 % 256),	/* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T1_VBP_F1 / 256),	/* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T1_VFP_F1 % 256),	/* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T1_VFP_F1 / 256),	/* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T1_HBP % 256),	/* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T1_HBP / 256),	/* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T1_HFP % 256),	/* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T1_HFP / 256),	/* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x33,	/* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T1_VIC,	/* vic ,0x10: 1080P ;  0x04 : 720P */
	}, {
		3, 0x92, 0x18, (u8)(T2_HSW % 256),	/* hsync  */
		3, 0x92, 0x19, (u8)(T2_VSW_F1 % 256),	/* vsync  */
		3, 0x92, 0x1c, (u8)(T2_HACT % 256),	/* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T2_HACT / 256),	/* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T2_HBP + T2_HFP + T2_HSW + T2_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T2_HBP + T2_HFP + T2_HSW + T2_HACT) / 256),
		3, 0x92, 0x36, (u8)((T2_VT_F1) % 256),	/* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T2_VT_F1) / 256),	/* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T2_VBP_F1 % 256),	/* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T2_VBP_F1 / 256),	/* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T2_VFP_F1 % 256),	/* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T2_VFP_F1 / 256),	/* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T2_HBP % 256),	/* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T2_HBP / 256),	/* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T2_HFP % 256),	/* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T2_HFP / 256),	/* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x22,	/* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T2_VIC,	/* vic ,0x10: 1080P ;  0x04 : 720P */
	}, {
		3, 0x92, 0x18, (u8)(T3_HSW % 256),	/* hsync  */
		3, 0x92, 0x19, (u8)(T3_VSW_F1 % 256),	/* vsync  */
		3, 0x92, 0x1c, (u8)(T3_HACT % 256),	/* H_active[7:0]  */
		3, 0x92, 0x1d, (u8)(T3_HACT / 256),	/* H_active[15:8]  */
		/* H_total[7:0] */
		3, 0x92, 0x34, (u8)((T3_HBP + T3_HFP + T3_HSW + T3_HACT) % 256),
		/* H_total[15:8] */
		3, 0x92, 0x35, (u8)((T3_HBP + T3_HFP + T3_HSW + T3_HACT) / 256),
		3, 0x92, 0x36, (u8)((T3_VT_F1) % 256),	/* V_total[7:0] */
		3, 0x92, 0x37, (u8)((T3_VT_F1) / 256),	/* V_total[15:8] */
		3, 0x92, 0x38, (u8)(T3_VBP_F1 % 256),	/* VBP[7:0] */
		3, 0x92, 0x39, (u8)(T3_VBP_F1 / 256),	/* VBP[15:8] */
		3, 0x92, 0x3a, (u8)(T3_VFP_F1 % 256),	/* VFP[7:0] */
		3, 0x92, 0x3b, (u8)(T3_VFP_F1 / 256),	/* VFP[15:8] */
		3, 0x92, 0x3c, (u8)(T3_HBP % 256),	/* T1_HBP[7:0] */
		3, 0x92, 0x3d, (u8)(T3_HBP / 256),	/* T1_HBP[15:8] */
		3, 0x92, 0x3e, (u8)(T3_HFP % 256),	/* T1_HFP[7:0] */
		3, 0x92, 0x3f, (u8)(T3_HFP / 256),	/* T1_HFP[15:8] */
		/*
		 * reg[0x43] is checksums, it will be changed
		 * after reg[0x45] or reg[0x47] changed.
		 * regs 0x43, 0x44, 0x45, 0x47 total sum is 0x6F.
		 */
		3, 0x94, 0x43, 0x25,	/* avi packet checksum ,avi_pb0 */
		3, 0x94, 0x47, T3_VIC,	/* vic ,0x10: 1080P ;  0x04 : 720P */
	},
};

static const struct drm_display_mode lt8912b_mode[MODES_NUM] = {
	{
		/* dclk_freq */
		.clock = T1_DCLK / 1000,
		/* width */
		.hdisplay = T1_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T1_WIDTH + T1_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T1_WIDTH + T1_HFP + T1_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T1_WIDTH + T1_HFP + T1_HSW + T1_HBP,
		/* height */
		.vdisplay = T1_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T1_HEIGHT + T1_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T1_HEIGHT + T1_VFP_F1 + T1_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T1_HEIGHT + T1_VFP_F1 + T1_VSW_F1 + T1_VBP_F1,
		.vrefresh = 60,
		.type = DRM_MODE_TYPE_PREFERRED,
	}, {
		/* dclk_freq */
		.clock = T2_DCLK / 1000,
		/* width */
		.hdisplay = T2_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T2_WIDTH + T2_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T2_WIDTH + T2_HFP + T2_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T2_WIDTH + T2_HFP + T2_HSW + T2_HBP,
		/* height */
		.vdisplay = T2_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T2_HEIGHT + T2_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T2_HEIGHT + T2_VFP_F1 + T2_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T2_HEIGHT + T2_VFP_F1 + T2_VSW_F1 + T2_VBP_F1,
		.vrefresh = 50,
	}, {
		/* dclk_freq */
		.clock = T3_DCLK / 1000,
		/* width */
		.hdisplay = T3_WIDTH,
		/* hsync_start = hdisplay + hfp */
		.hsync_start = T3_WIDTH + T3_HFP,
		/* hsync_end = hdisplay + hfp + hsw */
		.hsync_end = T3_WIDTH + T3_HFP + T3_HSW,
		/* htotal = hdisplay + hfp + hsw + hbp */
		.htotal = T3_WIDTH + T3_HFP + T3_HSW + T3_HBP,
		/* height */
		.vdisplay = T3_HEIGHT,
		/* vsync_start = vdisplay + vfp */
		.vsync_start = T3_HEIGHT + T3_VFP_F1,
		/* vsync_end = vdisplay + vfp + vsw */
		.vsync_end = T3_HEIGHT + T3_VFP_F1 + T3_VSW_F1,
		/* vtotal = vdisplay + vfp + vsw + vbp */
		.vtotal = T3_HEIGHT + T3_VFP_F1 + T3_VSW_F1 + T3_VBP_F1,
		.vrefresh = 60,
	},
};

static inline struct bridge_lt8912b *to_bridge_lt8912b_by_bridge(
	struct drm_bridge *bridge)
{
	return container_of(bridge, struct bridge_lt8912b, bridge);
}

static inline struct bridge_lt8912b *to_bridge_lt8912b_by_connector(
	struct drm_connector *con)
{
	return container_of(con, struct bridge_lt8912b, connector);
}

static struct i2c_client *bridge_get_i2c_client(const unsigned short i2c_addr)
{
	const __u32 num = ARRAY_SIZE(bridge_i2c_client);
	__u32 i;

	for (i = 0; i < num; i++) {
		struct i2c_client *client = bridge_i2c_client[i];

		if (client && (client->addr == i2c_addr))
			return client;
	}

	return NULL;
}

static s32 bridge_i2c_write_reg_array(u32 tidx, u32 *reg, u32 len)
{
	u32 i = 0;

	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);

	while (i < len) {
		if (reg[i] == DCS_MDELAY_FLAG) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else {
			char buf[4];
			struct i2c_client *client =
				bridge_get_i2c_client(reg[i + 1] >> 1);

			if (client) {
				buf[0] = reg[i + 2]; /* reg addr */
				buf[1] = reg[i + 3]; /* reg value */

				if (i2c_master_send(client, buf, 2) != 2) {
					DRM_ERROR("%d\n", __LINE__);
					return -1;
				}
			} else {
				pr_err("%d: null i2c_client @%x\n",
					__LINE__, reg[i + 1] >> 1);
			}
			i += reg[i] + 1;
		}
	}
	return 0;
}

static int bridge_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	__u32 i;
	int ret = 0;

	if (!client) {
		DRM_ERROR("%d: null i2c_client\n", __LINE__);
		return -1;
	}

	bridge_i2c_client[0] = client;
	for (i = 1; i < ARRAY_SIZE(bridge_i2c_client); i++) {
		bridge_i2c_client[i] = i2c_new_dummy(client->adapter,
					client->addr + i);
		if (!bridge_i2c_client[i]) {
			dev_err(&client->adapter->dev,
				"address 0x%02x unavailable\n",
				client->addr + i);
			ret = -EADDRINUSE;
			break;
		}
		dev_info(&client->adapter->dev,
			"i2c_new_dummy 0x%02x 0x%02x ok\n",
			client->addr + i, bridge_i2c_client[i]->addr);
	}

	if (!ret)
		dev_info(&client->adapter->dev,
			"attach bridge lt8912b into i2c adapter successful\n");

	return ret;
}

static int bridge_i2c_remove(struct i2c_client *client)
{
	dev_info(&client->adapter->dev,
		"detached bridge lt8912b from i2c adapter successfully\n");

	return 0;
}

static const struct i2c_device_id bridge_i2c_id[] = {
	{ "lt8912b_i2c", 0 },
	{ },
};

static const struct of_device_id bridge_i2c_match_types[] = {
	{
		.compatible = "lt8912b_i2c",
	}, {
		/* end node */
	}
};

struct i2c_driver bridge_i2c_driver = {
	.driver = {
		.name = "lt8912b_i2c",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(bridge_i2c_match_types),
	},
	.id_table = bridge_i2c_id,
	.probe = bridge_i2c_probe,
	.remove = bridge_i2c_remove,
	.command = NULL,
};

static int bridge_lt8912b_connector_get_modes(struct drm_connector *connector)
{
	struct bridge_lt8912b *bridge =
		to_bridge_lt8912b_by_connector(connector);

	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	for (i = 0; i < bridge->num_modes; i++) {
		const struct drm_display_mode *m = bridge->modes + i;

		mode = drm_mode_duplicate(bridge->drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);

		drm_mode_probed_add(connector, mode);
		num++;
		DRM_DEBUG_KMS("add mode[%d] %p\n", i, m);
	}

	connector->display_info.bpc = 6;
	connector->display_info.width_mm = 300;
	connector->display_info.height_mm = 150;

	return num;
}

static const struct drm_connector_helper_funcs
	bridge_lt8912b_connector_helper_funcs = {
	.get_modes = bridge_lt8912b_connector_get_modes,
};

static const struct drm_connector_funcs bridge_lt8912b_connector_funcs = {
	.reset = drm_atomic_helper_connector_reset,
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = drm_connector_cleanup,
	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static int bridge_lt8912b_attach(struct drm_bridge *bridge)
{
	struct bridge_lt8912b *lt8912b = to_bridge_lt8912b_by_bridge(bridge);
	struct drm_connector *connector = &lt8912b->connector;
	int ret;

	if (!bridge->encoder) {
		DRM_ERROR("Missing encoder\n");
		return -ENODEV;
	}
	lt8912b->drm = bridge->encoder->dev;
	drm_connector_helper_add(connector,
				 &bridge_lt8912b_connector_helper_funcs);

	ret = drm_connector_init(lt8912b->drm, connector,
				 &bridge_lt8912b_connector_funcs,
				 DRM_MODE_CONNECTOR_DSI);
	if (ret) {
		DRM_ERROR("Failed to initialize connector\n");
		return ret;
	}

	drm_connector_attach_encoder(&lt8912b->connector,
					  bridge->encoder);

	return 0;
}

static void bridge_lt8912b_detach(struct drm_bridge *bridge)
{
	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
}

bool bridge_lt8912b_mode_fixup(struct drm_bridge *bridge,
			   const struct drm_display_mode *mode,
			   struct drm_display_mode *adjusted_mode)
{
	unsigned int i;
	struct bridge_lt8912b *lt8912b = to_bridge_lt8912b_by_bridge(bridge);

	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);

	lt8912b->modes_idx = 0;
	adjusted_mode->private = lt8912b->modes->private;

	/* should fix dsi set ref_clk rate while use bridge */
	for (i = 0; i < lt8912b->num_modes; i++) {
		const struct drm_display_mode *m = lt8912b->modes;

		if (drm_mode_equal_no_clocks(mode, m + i)) {
			DRM_DEBUG_KMS("mode[%d]_private=%p\n",
					i, (m + i)->private);

			lt8912b->modes_idx = i;
			adjusted_mode->private = (m + i)->private;
			break;
		}
	}

	return true;
}

static inline int lt8912b_is_sync_boot(struct drm_bridge *bridge)
{
	struct lombo_drv *drv = bridge->dev->dev_private;
	struct lombo_boot_disp *bd = drv->boot_disp;
	struct drm_encoder *enc = bridge->encoder;

	if (bd && enc && bd->crtc && (bd->crtc == enc->crtc))
		return 1;
	return 0;
}

void bridge_lt8912b_mode_set(struct drm_bridge *bridge,
	struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode)
{
	struct bridge_lt8912b *lt8912b = to_bridge_lt8912b_by_bridge(bridge);

	if (lt8912b_is_sync_boot(bridge)) {
		DRM_DEBUG("skip for sync boot\n");
		return;
	}

	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
	if (lt8912b->power_gpio) {
		gpio_direction_output(lt8912b->power_gpio, 1);
		msleep(100);
	}

	if (lt8912b->sel_gpio) {
		gpio_direction_output(lt8912b->sel_gpio, 1);
		msleep(50);
	}

	if (lt8912b->reset_gpio) {
		gpio_direction_output(lt8912b->reset_gpio, 1);
		msleep(100);
		gpio_direction_output(lt8912b->reset_gpio, 0);
		msleep(100);
		gpio_direction_output(lt8912b->reset_gpio, 1);
		msleep(100);
	}

	bridge_i2c_write_reg_array(3,
		lt8912b_init_common_code,
		ARRAY_SIZE(lt8912b_init_common_code));

	bridge_i2c_write_reg_array(3,
		lt8912b_init_code[lt8912b->modes_idx],
		ARRAY_SIZE(lt8912b_init_code[lt8912b->modes_idx]));
}

static void bridge_lt8912b_pre_enable(struct drm_bridge *bridge)
{
	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
}

static void bridge_lt8912b_enable(struct drm_bridge *bridge)
{
	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
}

static void bridge_lt8912b_disable(struct drm_bridge *bridge)
{
	struct bridge_lt8912b *lt8912b = to_bridge_lt8912b_by_bridge(bridge);

	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
	if (lt8912b->reset_gpio)
		gpio_direction_output(lt8912b->reset_gpio, 0);

	if (lt8912b->power_gpio)
		gpio_direction_output(lt8912b->power_gpio, 0);
}

static void bridge_lt8912b_post_disable(struct drm_bridge *bridge)
{
	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
}

static const struct drm_bridge_funcs bridge_lt8912b_funcs = {
	.attach = bridge_lt8912b_attach,
	.detach = bridge_lt8912b_detach,
	.mode_fixup = bridge_lt8912b_mode_fixup,
	.mode_set = bridge_lt8912b_mode_set,
	.pre_enable = bridge_lt8912b_pre_enable,
	.enable = bridge_lt8912b_enable,
	.disable = bridge_lt8912b_disable,
	.post_disable = bridge_lt8912b_post_disable,
};

static const struct of_device_id bridge_lt8912b_of_match[] = {
	{
		.compatible = "lt8912b",
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, bridge_lt8912b_of_match);

static int bridge_lt8912b_probe(struct mipi_dsi_device *dsi)
{
	struct device *dev = &dsi->dev;
	struct device_node *np = dev->of_node;
	struct bridge_lt8912b *bridge;
	enum of_gpio_flags flags;
	int err;
	const struct of_device_id *id;

	DRM_DEBUG_DRIVER("%s,%d\n", __FILE__, __LINE__);

	if (!of_device_is_available(dsi->dev.of_node))
		return -ENODEV;

	id = of_match_node(bridge_lt8912b_of_match, dsi->dev.of_node);
	if (!id)
		return -ENODEV;

	bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
	if (!bridge)
		return -ENOMEM;

	if (i2c_add_driver(&bridge_i2c_driver)) {
		DRM_ERROR("failed to register panel i2c driver\n");
		return -ENOENT;
	}

	bridge->power_gpio = of_get_named_gpio_flags(
		np, "power_gpio", 0, &flags);
	DRM_DEBUG_DRIVER("power_gpio %d, flags 0x%x\n",
		bridge->power_gpio, flags);
	if (!gpio_is_valid(bridge->power_gpio)) {
		DRM_ERROR("power_gpio %d invalid\n", bridge->power_gpio);
		bridge->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, bridge->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				bridge->power_gpio);
			bridge->power_gpio = 0;
		}
	}

	bridge->sel_gpio =
		of_get_named_gpio_flags(np, "sel_gpio", 0, &flags);
	DRM_DEBUG_KMS("sel_gpio %d, flags 0x%x\n", bridge->sel_gpio, flags);
	if (!gpio_is_valid(bridge->sel_gpio)) {
		DRM_DEBUG_KMS("sel_gpio %d invalid\n", bridge->sel_gpio);
		bridge->sel_gpio = 0;
	} else {
		err = devm_gpio_request(dev, bridge->sel_gpio, "sel_gpio");
		if (err < 0) {
			DRM_DEBUG_KMS("sel_gpio %d request failed\n",
				bridge->sel_gpio);
			bridge->sel_gpio = 0;
		}
	}

	bridge->reset_gpio = of_get_named_gpio_flags(
		np, "reset_gpio", 0, &flags);
	DRM_DEBUG_DRIVER("reset_gpio %d, flags 0x%x\n",
		bridge->reset_gpio, flags);
	if (!gpio_is_valid(bridge->reset_gpio)) {
		DRM_ERROR("reset_gpio %d invalid\n", bridge->reset_gpio);
		bridge->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, bridge->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				bridge->reset_gpio);
			bridge->reset_gpio = 0;
		}
	}

	bridge->dev = dev;
	bridge->bridge.funcs = &bridge_lt8912b_funcs;
	bridge->bridge.of_node = dev->of_node;
	bridge->modes = &lt8912b_mode[0];
	bridge->num_modes = ARRAY_SIZE(lt8912b_mode);
	drm_bridge_add(&bridge->bridge);

	dsi->lanes = 4;
	dsi->format = MIPI_DSI_FMT_RGB888;
	dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
	err = mipi_dsi_attach(dsi);
	if (err)
		return err;

	dev_set_drvdata(dev, bridge);
	DRM_DEBUG_DRIVER("[%d] over.\n", __LINE__);

	return 0;
}

static int bridge_lt8912b_remove(struct mipi_dsi_device *dsi)
{
	struct bridge_lt8912b *bridge = dev_get_drvdata(&dsi->dev);

	DRM_DEBUG_KMS("%s,%d", __func__, __LINE__);
	mipi_dsi_detach(dsi);
	drm_bridge_remove(&bridge->bridge);
	return 0;
}

static struct mipi_dsi_driver bridge_lt8912b_mipi_dsi_driver = {
	.driver = {
		.name = "bridge-lt8912b",
		.of_match_table = bridge_lt8912b_of_match,
	},
	.probe = bridge_lt8912b_probe,
	.remove = bridge_lt8912b_remove,
};

static int __init bridge_lt8912b_init(void)
{
	int err;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	err = mipi_dsi_driver_register(&bridge_lt8912b_mipi_dsi_driver);
	if (err < 0)
		return err;

	return 0;
}
module_init(bridge_lt8912b_init);

static void __exit bridge_lt8912b_exit(void)
{
	mipi_dsi_driver_unregister(&bridge_lt8912b_mipi_dsi_driver);
}
module_exit(bridge_lt8912b_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("DRM Driver for Bridge lt8912b");
MODULE_LICENSE("GPL and additional rights");
